/* eslint-disable */

//
// NOTE(kevinb): This file was originally part of mhchem.js, but
// was extracted so that it could be mocked for testing purposes.
//

//
//  This is the main function for handing the \ce and \pu commands.
//  It takes the argument to \ce or \pu and returns the corresponding TeX string.
//

export const chemParse = function (tokens, stateMachine) {
    // Recreate the argument string from KaTeX's array of tokens.
    let str = "";
    let expectedLoc = tokens[tokens.length - 1].loc.start;
    for (let i = tokens.length - 1; i >= 0; i--) {
        if (tokens[i].loc.start > expectedLoc) {
            // context.consumeArgs has eaten a space.
            str += " ";
            expectedLoc = tokens[i].loc.start;
        }
        str += tokens[i].text;
        expectedLoc += tokens[i].text.length;
    }
    const tex = texify.go(mhchemParser.go(str, stateMachine));
    return tex;
};

//
// Core parser for mhchem syntax  (recursive)
//
/** @type {MhchemParser} */
var mhchemParser = {
    //
    // Parses mchem \ce syntax
    //
    // Call like
    //   go("H2O");
    //
    go: function (input, stateMachine) {
        if (!input) {
            return [];
        }
        if (stateMachine === undefined) {
            stateMachine = "ce";
        }
        let state = "0";

        //
        // String buffers for parsing:
        //
        // buffer.a == amount
        // buffer.o == element
        // buffer.b == left-side superscript
        // buffer.p == left-side subscript
        // buffer.q == right-side subscript
        // buffer.d == right-side superscript
        //
        // buffer.r == arrow
        // buffer.rdt == arrow, script above, type
        // buffer.rd == arrow, script above, content
        // buffer.rqt == arrow, script below, type
        // buffer.rq == arrow, script below, content
        //
        // buffer.text_
        // buffer.rm
        // etc.
        //
        // buffer.parenthesisLevel == int, starting at 0
        // buffer.sb == bool, space before
        // buffer.beginsWithBond == bool
        //
        // These letters are also used as state names.
        //
        // Other states:
        // 0 == begin of main part (arrow/operator unlikely)
        // 1 == next entity
        // 2 == next entity (arrow/operator unlikely)
        // 3 == next atom
        // c == macro
        //
        /** @type {Buffer} */
        const buffer = {};
        buffer["parenthesisLevel"] = 0;

        input = input.replace(/\n/g, " ");
        input = input.replace(/[\u2212\u2013\u2014\u2010]/g, "-");
        input = input.replace(/[\u2026]/g, "...");

        //
        // Looks through mhchemParser.transitions, to execute a matching action
        // (recursive)
        //
        let lastInput;
        let watchdog = 10;
        /** @type {ParserOutput[]} */
        const output = [];
        while (true) {
            if (lastInput !== input) {
                watchdog = 10;
                lastInput = input;
            } else {
                watchdog--;
            }
            //
            // Find actions in transition table
            //
            const machine = mhchemParser.stateMachines[stateMachine];
            const t = machine.transitions[state] || machine.transitions["*"];
            iterateTransitions: for (let i = 0; i < t.length; i++) {
                const matches = mhchemParser.patterns.match_(
                    t[i].pattern,
                    input,
                );
                if (matches) {
                    //
                    // Execute actions
                    //
                    const task = t[i].task;
                    for (let iA = 0; iA < task.action_.length; iA++) {
                        var o;
                        //
                        // Find and execute action
                        //
                        if (machine.actions[task.action_[iA].type_]) {
                            o = machine.actions[task.action_[iA].type_](
                                buffer,
                                matches.match_,
                                task.action_[iA].option,
                            );
                        } else if (
                            mhchemParser.actions[task.action_[iA].type_]
                        ) {
                            o = mhchemParser.actions[task.action_[iA].type_](
                                buffer,
                                matches.match_,
                                task.action_[iA].option,
                            );
                        } else {
                            throw [
                                "MhchemBugA",
                                "mhchem bug A. Please report. (" +
                                    task.action_[iA].type_ +
                                    ")",
                            ]; // Trying to use non-existing action
                        }
                        //
                        // Add output
                        //
                        mhchemParser.concatArray(output, o);
                    }
                    //
                    // Set next state,
                    // Shorten input,
                    // Continue with next character
                    //   (= apply only one transition per position)
                    //
                    state = task.nextState || state;
                    if (input.length > 0) {
                        if (!task.revisit) {
                            input = matches.remainder;
                        }
                        if (!task.toContinue) {
                            break iterateTransitions;
                        }
                    } else {
                        return output;
                    }
                }
            }
            //
            // Prevent infinite loop
            //
            if (watchdog <= 0) {
                throw ["MhchemBugU", "mhchem bug U. Please report."]; // Unexpected character
            }
        }
    },
    concatArray: function (a, b) {
        if (b) {
            if (Array.isArray(b)) {
                for (let iB = 0; iB < b.length; iB++) {
                    a.push(b[iB]);
                }
            } else {
                a.push(b);
            }
        }
    },

    patterns: {
        //
        // Matching patterns
        // either regexps or function that return null or {match_:"a", remainder:"bc"}
        //
        patterns: {
            // property names must not look like integers ("2") for correct property traversal order, later on
            empty: /^$/,
            else: /^./,
            else2: /^./,
            space: /^\s/,
            "space A": /^\s(?=[A-Z\\$])/,
            space$: /^\s$/,
            "a-z": /^[a-z]/,
            x: /^x/,
            x$: /^x$/,
            i$: /^i$/,
            letters:
                /^(?:[a-zA-Z\u03B1-\u03C9\u0391-\u03A9?@]|(?:\\(?:alpha|beta|gamma|delta|epsilon|zeta|eta|theta|iota|kappa|lambda|mu|nu|xi|omicron|pi|rho|sigma|tau|upsilon|phi|chi|psi|omega|Gamma|Delta|Theta|Lambda|Xi|Pi|Sigma|Upsilon|Phi|Psi|Omega)(?:\s+|\{\}|(?![a-zA-Z]))))+/,
            "\\greek":
                /^\\(?:alpha|beta|gamma|delta|epsilon|zeta|eta|theta|iota|kappa|lambda|mu|nu|xi|omicron|pi|rho|sigma|tau|upsilon|phi|chi|psi|omega|Gamma|Delta|Theta|Lambda|Xi|Pi|Sigma|Upsilon|Phi|Psi|Omega)(?:\s+|\{\}|(?![a-zA-Z]))/,
            "one lowercase latin letter $": /^(?:([a-z])(?:$|[^a-zA-Z]))$/,
            "$one lowercase latin letter$ $":
                /^\$(?:([a-z])(?:$|[^a-zA-Z]))\$$/,
            "one lowercase greek letter $":
                /^(?:\$?[\u03B1-\u03C9]\$?|\$?\\(?:alpha|beta|gamma|delta|epsilon|zeta|eta|theta|iota|kappa|lambda|mu|nu|xi|omicron|pi|rho|sigma|tau|upsilon|phi|chi|psi|omega)\s*\$?)(?:\s+|\{\}|(?![a-zA-Z]))$/,
            digits: /^[0-9]+/,
            "-9.,9": /^[+\-]?(?:[0-9]+(?:[,.][0-9]+)?|[0-9]*(?:\.[0-9]+))/,
            "-9.,9 no missing 0": /^[+\-]?[0-9]+(?:[.,][0-9]+)?/,
            "(-)(9.,9)(e)(99)": function (input) {
                const m = input.match(
                    /^(\+\-|\+\/\-|\+|\-|\\pm\s?)?([0-9]+(?:[,.][0-9]+)?|[0-9]*(?:\.[0-9]+))?(\((?:[0-9]+(?:[,.][0-9]+)?|[0-9]*(?:\.[0-9]+))\))?(?:([eE]|\s*(\*|x|\\times|\u00D7)\s*10\^)([+\-]?[0-9]+|\{[+\-]?[0-9]+\}))?/,
                );
                if (m && m[0]) {
                    return {
                        match_: m.splice(1),
                        remainder: input.substr(m[0].length),
                    };
                }
                return null;
            },
            "(-)(9)^(-9)": function (input) {
                const m = input.match(
                    /^(\+\-|\+\/\-|\+|\-|\\pm\s?)?([0-9]+(?:[,.][0-9]+)?|[0-9]*(?:\.[0-9]+)?)\^([+\-]?[0-9]+|\{[+\-]?[0-9]+\})/,
                );
                if (m && m[0]) {
                    return {
                        match_: m.splice(1),
                        remainder: input.substr(m[0].length),
                    };
                }
                return null;
            },
            "state of aggregation $": function (input) {
                // ... or crystal system
                const a = mhchemParser.patterns.findObserveGroups(
                    input,
                    "",
                    /^\([a-z]{1,3}(?=[\),])/,
                    ")",
                    "",
                ); // (aq), (aq,$\infty$), (aq, sat)
                if (a && a.remainder.match(/^($|[\s,;\)\]\}])/)) {
                    return a;
                } //  AND end of 'phrase'
                const m = input.match(/^(?:\((?:\\ca\s?)?\$[amothc]\$\))/); // OR crystal system ($o$) (\ca$c$)
                if (m) {
                    return {match_: m[0], remainder: input.substr(m[0].length)};
                }
                return null;
            },
            "_{(state of aggregation)}$": /^_\{(\([a-z]{1,3}\))\}/,
            "{[(": /^(?:\\\{|\[|\()/,
            ")]}": /^(?:\)|\]|\\\})/,
            ", ": /^[,;]\s*/,
            ",": /^[,;]/,
            ".": /^[.]/,
            ". ": /^([.\u22C5\u00B7\u2022])\s*/,
            "...": /^\.\.\.(?=$|[^.])/,
            "* ": /^([*])\s*/,
            "^{(...)}": function (input) {
                return mhchemParser.patterns.findObserveGroups(
                    input,
                    "^{",
                    "",
                    "",
                    "}",
                );
            },
            "^($...$)": function (input) {
                return mhchemParser.patterns.findObserveGroups(
                    input,
                    "^",
                    "$",
                    "$",
                    "",
                );
            },
            "^a": /^\^([0-9]+|[^\\_])/,
            "^\\x{}{}": function (input) {
                return mhchemParser.patterns.findObserveGroups(
                    input,
                    "^",
                    /^\\[a-zA-Z]+\{/,
                    "}",
                    "",
                    "",
                    "{",
                    "}",
                    "",
                    true,
                );
            },
            "^\\x{}": function (input) {
                return mhchemParser.patterns.findObserveGroups(
                    input,
                    "^",
                    /^\\[a-zA-Z]+\{/,
                    "}",
                    "",
                );
            },
            "^\\x": /^\^(\\[a-zA-Z]+)\s*/,
            "^(-1)": /^\^(-?\d+)/,
            "'": /^'/,
            "_{(...)}": function (input) {
                return mhchemParser.patterns.findObserveGroups(
                    input,
                    "_{",
                    "",
                    "",
                    "}",
                );
            },
            "_($...$)": function (input) {
                return mhchemParser.patterns.findObserveGroups(
                    input,
                    "_",
                    "$",
                    "$",
                    "",
                );
            },
            _9: /^_([+\-]?[0-9]+|[^\\])/,
            "_\\x{}{}": function (input) {
                return mhchemParser.patterns.findObserveGroups(
                    input,
                    "_",
                    /^\\[a-zA-Z]+\{/,
                    "}",
                    "",
                    "",
                    "{",
                    "}",
                    "",
                    true,
                );
            },
            "_\\x{}": function (input) {
                return mhchemParser.patterns.findObserveGroups(
                    input,
                    "_",
                    /^\\[a-zA-Z]+\{/,
                    "}",
                    "",
                );
            },
            "_\\x": /^_(\\[a-zA-Z]+)\s*/,
            "^_": /^(?:\^(?=_)|\_(?=\^)|[\^_]$)/,
            "{}": /^\{\}/,
            "{...}": function (input) {
                return mhchemParser.patterns.findObserveGroups(
                    input,
                    "",
                    "{",
                    "}",
                    "",
                );
            },
            "{(...)}": function (input) {
                return mhchemParser.patterns.findObserveGroups(
                    input,
                    "{",
                    "",
                    "",
                    "}",
                );
            },
            "$...$": function (input) {
                return mhchemParser.patterns.findObserveGroups(
                    input,
                    "",
                    "$",
                    "$",
                    "",
                );
            },
            "${(...)}$": function (input) {
                return mhchemParser.patterns.findObserveGroups(
                    input,
                    "${",
                    "",
                    "",
                    "}$",
                );
            },
            "$(...)$": function (input) {
                return mhchemParser.patterns.findObserveGroups(
                    input,
                    "$",
                    "",
                    "",
                    "$",
                );
            },
            "=<>": /^[=<>]/,
            "#": /^[#\u2261]/,
            "+": /^\+/,
            "-$": /^-(?=[\s_},;\]/]|$|\([a-z]+\))/, // -space -, -; -] -/ -$ -state-of-aggregation
            "-9": /^-(?=[0-9])/,
            "- orbital overlap": /^-(?=(?:[spd]|sp)(?:$|[\s,;\)\]\}]))/,
            "-": /^-/,
            "pm-operator": /^(?:\\pm|\$\\pm\$|\+-|\+\/-)/,
            operator:
                /^(?:\+|(?:[\-=<>]|<<|>>|\\approx|\$\\approx\$)(?=\s|$|-?[0-9]))/,
            arrowUpDown: /^(?:v|\(v\)|\^|\(\^\))(?=$|[\s,;\)\]\}])/,
            "\\bond{(...)}": function (input) {
                return mhchemParser.patterns.findObserveGroups(
                    input,
                    "\\bond{",
                    "",
                    "",
                    "}",
                );
            },
            "->": /^(?:<->|<-->|->|<-|<=>>|<<=>|<=>|[\u2192\u27F6\u21CC])/,
            CMT: /^[CMT](?=\[)/,
            "[(...)]": function (input) {
                return mhchemParser.patterns.findObserveGroups(
                    input,
                    "[",
                    "",
                    "",
                    "]",
                );
            },
            "1st-level escape": /^(&|\\\\|\\hline)\s*/,
            "\\,": /^(?:\\[,\ ;:])/, // \\x - but output no space before
            "\\x{}{}": function (input) {
                return mhchemParser.patterns.findObserveGroups(
                    input,
                    "",
                    /^\\[a-zA-Z]+\{/,
                    "}",
                    "",
                    "",
                    "{",
                    "}",
                    "",
                    true,
                );
            },
            "\\x{}": function (input) {
                return mhchemParser.patterns.findObserveGroups(
                    input,
                    "",
                    /^\\[a-zA-Z]+\{/,
                    "}",
                    "",
                );
            },
            "\\ca": /^\\ca(?:\s+|(?![a-zA-Z]))/,
            "\\x": /^(?:\\[a-zA-Z]+\s*|\\[_&{}%])/,
            orbital: /^(?:[0-9]{1,2}[spdfgh]|[0-9]{0,2}sp)(?=$|[^a-zA-Z])/, // only those with numbers in front, because the others will be formatted correctly anyway
            others: /^[\/~|]/,
            "\\frac{(...)}": function (input) {
                return mhchemParser.patterns.findObserveGroups(
                    input,
                    "\\frac{",
                    "",
                    "",
                    "}",
                    "{",
                    "",
                    "",
                    "}",
                );
            },
            "\\overset{(...)}": function (input) {
                return mhchemParser.patterns.findObserveGroups(
                    input,
                    "\\overset{",
                    "",
                    "",
                    "}",
                    "{",
                    "",
                    "",
                    "}",
                );
            },
            "\\underset{(...)}": function (input) {
                return mhchemParser.patterns.findObserveGroups(
                    input,
                    "\\underset{",
                    "",
                    "",
                    "}",
                    "{",
                    "",
                    "",
                    "}",
                );
            },
            "\\underbrace{(...)}": function (input) {
                return mhchemParser.patterns.findObserveGroups(
                    input,
                    "\\underbrace{",
                    "",
                    "",
                    "}_",
                    "{",
                    "",
                    "",
                    "}",
                );
            },
            "\\color{(...)}0": function (input) {
                return mhchemParser.patterns.findObserveGroups(
                    input,
                    "\\color{",
                    "",
                    "",
                    "}",
                );
            },
            "\\color{(...)}{(...)}1": function (input) {
                return mhchemParser.patterns.findObserveGroups(
                    input,
                    "\\color{",
                    "",
                    "",
                    "}",
                    "{",
                    "",
                    "",
                    "}",
                );
            },
            "\\color(...){(...)}2": function (input) {
                return mhchemParser.patterns.findObserveGroups(
                    input,
                    "\\color",
                    "\\",
                    "",
                    /^(?=\{)/,
                    "{",
                    "",
                    "",
                    "}",
                );
            },
            "\\ce{(...)}": function (input) {
                return mhchemParser.patterns.findObserveGroups(
                    input,
                    "\\ce{",
                    "",
                    "",
                    "}",
                );
            },
            oxidation$: /^(?:[+-][IVX]+|\\pm\s*0|\$\\pm\$\s*0)$/,
            "d-oxidation$": /^(?:[+-]?\s?[IVX]+|\\pm\s*0|\$\\pm\$\s*0)$/, // 0 could be oxidation or charge
            "roman numeral": /^[IVX]+/,
            "1/2$": /^[+\-]?(?:[0-9]+|\$[a-z]\$|[a-z])\/[0-9]+(?:\$[a-z]\$|[a-z])?$/,
            amount: function (input) {
                let match;
                // e.g. 2, 0.5, 1/2, -2, n/2, +;  $a$ could be added later in parsing
                match = input.match(
                    /^(?:(?:(?:\([+\-]?[0-9]+\/[0-9]+\)|[+\-]?(?:[0-9]+|\$[a-z]\$|[a-z])\/[0-9]+|[+\-]?[0-9]+[.,][0-9]+|[+\-]?\.[0-9]+|[+\-]?[0-9]+)(?:[a-z](?=\s*[A-Z]))?)|[+\-]?[a-z](?=\s*[A-Z])|\+(?!\s))/,
                );
                if (match) {
                    return {
                        match_: match[0],
                        remainder: input.substr(match[0].length),
                    };
                }
                const a = mhchemParser.patterns.findObserveGroups(
                    input,
                    "",
                    "$",
                    "$",
                    "",
                );
                if (a) {
                    // e.g. $2n-1$, $-$
                    match = a.match_.match(
                        /^\$(?:\(?[+\-]?(?:[0-9]*[a-z]?[+\-])?[0-9]*[a-z](?:[+\-][0-9]*[a-z]?)?\)?|\+|-)\$$/,
                    );
                    if (match) {
                        return {
                            match_: match[0],
                            remainder: input.substr(match[0].length),
                        };
                    }
                }
                return null;
            },
            amount2: function (input) {
                return this["amount"](input);
            },
            "(KV letters),": /^(?:[A-Z][a-z]{0,2}|i)(?=,)/,
            formula$: function (input) {
                if (input.match(/^\([a-z]+\)$/)) {
                    return null;
                } // state of aggregation = no formula
                const match = input.match(
                    /^(?:[a-z]|(?:[0-9\ \+\-\,\.\(\)]+[a-z])+[0-9\ \+\-\,\.\(\)]*|(?:[a-z][0-9\ \+\-\,\.\(\)]+)+[a-z]?)$/,
                );
                if (match) {
                    return {
                        match_: match[0],
                        remainder: input.substr(match[0].length),
                    };
                }
                return null;
            },
            uprightEntities: /^(?:pH|pOH|pC|pK|iPr|iBu)(?=$|[^a-zA-Z])/,
            "/": /^\s*(\/)\s*/,
            "//": /^\s*(\/\/)\s*/,
            "*": /^\s*[*.]\s*/,
        },
        findObserveGroups: function (
            input,
            begExcl,
            begIncl,
            endIncl,
            endExcl,
            beg2Excl,
            beg2Incl,
            end2Incl,
            end2Excl,
            combine,
        ) {
            /** @type {{(input: string, pattern: string | RegExp): string | string[] | null;}} */
            const _match = function (input, pattern) {
                if (typeof pattern === "string") {
                    if (input.indexOf(pattern) !== 0) {
                        return null;
                    }
                    return pattern;
                }
                const match = input.match(pattern);
                if (!match) {
                    return null;
                }
                return match[0];
            };
            /** @type {{(input: string, i: number, endChars: string | RegExp): {endMatchBegin: number, endMatchEnd: number} | null;}} */
            const _findObserveGroups = function (input, i, endChars) {
                let braces = 0;
                while (i < input.length) {
                    const a = input.charAt(i);
                    const match = _match(input.substr(i), endChars);
                    if (match !== null && braces === 0) {
                        return {
                            endMatchBegin: i,
                            endMatchEnd: i + match.length,
                        };
                    }
                    if (a === "{") {
                        braces++;
                    } else if (a === "}") {
                        if (braces === 0) {
                            throw [
                                "ExtraCloseMissingOpen",
                                "Extra close brace or missing open brace",
                            ];
                        } else {
                            braces--;
                        }
                    }
                    i++;
                }
                if (braces > 0) {
                    return null;
                }
                return null;
            };
            let match = _match(input, begExcl);
            if (match === null) {
                return null;
            }
            input = input.substr(match.length);
            match = _match(input, begIncl);
            if (match === null) {
                return null;
            }
            const e = _findObserveGroups(
                input,
                match.length,
                endIncl || endExcl,
            );
            if (e === null) {
                return null;
            }
            const match1 = input.substring(
                0,
                endIncl ? e.endMatchEnd : e.endMatchBegin,
            );
            if (!(beg2Excl || beg2Incl)) {
                return {
                    match_: match1,
                    remainder: input.substr(e.endMatchEnd),
                };
            }
            const group2 = this.findObserveGroups(
                input.substr(e.endMatchEnd),
                beg2Excl,
                beg2Incl,
                end2Incl,
                end2Excl,
            );
            if (group2 === null) {
                return null;
            }
            /** @type {string[]} */
            const matchRet = [match1, group2.match_];
            return {
                match_: combine ? matchRet.join("") : matchRet,
                remainder: group2.remainder,
            };
        },

        //
        // Matching function
        // e.g. match("a", input) will look for the regexp called "a" and see if it matches
        // returns null or {match_:"a", remainder:"bc"}
        //
        match_: function (m, input) {
            const pattern = mhchemParser.patterns.patterns[m];
            if (pattern === undefined) {
                throw [
                    "MhchemBugP",
                    "mhchem bug P. Please report. (" + m + ")",
                ]; // Trying to use non-existing pattern
            } else if (typeof pattern === "function") {
                return mhchemParser.patterns.patterns[m](input); // cannot use cached var pattern here, because some pattern functions need this===mhchemParser
            } else {
                // RegExp
                const match = input.match(pattern);
                if (match) {
                    let mm;
                    if (match[2]) {
                        mm = [match[1], match[2]];
                    } else if (match[1]) {
                        mm = match[1];
                    } else {
                        mm = match[0];
                    }
                    return {
                        match_: mm,
                        remainder: input.substr(match[0].length),
                    };
                }
                return null;
            }
        },
    },

    //
    // Generic state machine actions
    //
    actions: {
        "a=": function (buffer, m) {
            buffer.a = (buffer.a || "") + m;
        },
        "b=": function (buffer, m) {
            buffer.b = (buffer.b || "") + m;
        },
        "p=": function (buffer, m) {
            buffer.p = (buffer.p || "") + m;
        },
        "o=": function (buffer, m) {
            buffer.o = (buffer.o || "") + m;
        },
        "q=": function (buffer, m) {
            buffer.q = (buffer.q || "") + m;
        },
        "d=": function (buffer, m) {
            buffer.d = (buffer.d || "") + m;
        },
        "rm=": function (buffer, m) {
            buffer.rm = (buffer.rm || "") + m;
        },
        "text=": function (buffer, m) {
            buffer.text_ = (buffer.text_ || "") + m;
        },
        insert: function (buffer, m, a) {
            return {type_: a};
        },
        "insert+p1": function (buffer, m, a) {
            return {type_: a, p1: m};
        },
        "insert+p1+p2": function (buffer, m, a) {
            return {type_: a, p1: m[0], p2: m[1]};
        },
        copy: function (buffer, m) {
            return m;
        },
        rm: function (buffer, m) {
            return {type_: "rm", p1: m || ""};
        },
        text: function (buffer, m) {
            return mhchemParser.go(m, "text");
        },
        "{text}": function (buffer, m) {
            const ret = ["{"];
            mhchemParser.concatArray(ret, mhchemParser.go(m, "text"));
            ret.push("}");
            return ret;
        },
        "tex-math": function (buffer, m) {
            return mhchemParser.go(m, "tex-math");
        },
        "tex-math tight": function (buffer, m) {
            return mhchemParser.go(m, "tex-math tight");
        },
        bond: function (buffer, m, k) {
            return {type_: "bond", kind_: k || m};
        },
        "color0-output": function (buffer, m) {
            return {type_: "color0", color: m[0]};
        },
        ce: function (buffer, m) {
            return mhchemParser.go(m);
        },
        "1/2": function (buffer, m) {
            /** @type {ParserOutput[]} */
            const ret = [];
            if (m.match(/^[+\-]/)) {
                ret.push(m.substr(0, 1));
                m = m.substr(1);
            }
            const n = m.match(
                /^([0-9]+|\$[a-z]\$|[a-z])\/([0-9]+)(\$[a-z]\$|[a-z])?$/,
            );
            n[1] = n[1].replace(/\$/g, "");
            ret.push({type_: "frac", p1: n[1], p2: n[2]});
            if (n[3]) {
                n[3] = n[3].replace(/\$/g, "");
                ret.push({type_: "tex-math", p1: n[3]});
            }
            return ret;
        },
        "9,9": function (buffer, m) {
            return mhchemParser.go(m, "9,9");
        },
    },
    //
    // createTransitions
    // convert  { 'letter': { 'state': { action_: 'output' } } }  to  { 'state' => [ { pattern: 'letter', task: { action_: [{type_: 'output'}] } } ] }
    // with expansion of 'a|b' to 'a' and 'b' (at 2 places)
    //
    createTransitions: function (o) {
        let pattern;
        let state;
        /** @type {string[]} */
        let stateArray;
        let i;
        //
        // 1. Collect all states
        //
        /** @type {Transitions} */
        const transitions = {};
        for (pattern in o) {
            for (state in o[pattern]) {
                stateArray = state.split("|");
                o[pattern][state].stateArray = stateArray;
                for (i = 0; i < stateArray.length; i++) {
                    transitions[stateArray[i]] = [];
                }
            }
        }
        //
        // 2. Fill states
        //
        for (pattern in o) {
            for (state in o[pattern]) {
                stateArray = o[pattern][state].stateArray || [];
                for (i = 0; i < stateArray.length; i++) {
                    //
                    // 2a. Normalize actions into array:  'text=' ==> [{type_:'text='}]
                    // (Note to myself: Resolving the function here would be problematic. It would need .bind (for *this*) and currying (for *option*).)
                    //
                    /** @type {any} */
                    const p = o[pattern][state];
                    if (p.action_) {
                        p.action_ = [].concat(p.action_);
                        for (let k = 0; k < p.action_.length; k++) {
                            if (typeof p.action_[k] === "string") {
                                p.action_[k] = {type_: p.action_[k]};
                            }
                        }
                    } else {
                        p.action_ = [];
                    }
                    //
                    // 2.b Multi-insert
                    //
                    const patternArray = pattern.split("|");
                    for (let j = 0; j < patternArray.length; j++) {
                        if (stateArray[i] === "*") {
                            // insert into all
                            for (const t in transitions) {
                                transitions[t].push({
                                    pattern: patternArray[j],
                                    task: p,
                                });
                            }
                        } else {
                            transitions[stateArray[i]].push({
                                pattern: patternArray[j],
                                task: p,
                            });
                        }
                    }
                }
            }
        }
        return transitions;
    },
    stateMachines: {},
};

//
// Definition of state machines
//
mhchemParser.stateMachines = {
    //
    // \ce state machines
    //
    //#region ce
    ce: {
        // main parser
        transitions: mhchemParser.createTransitions({
            empty: {
                "*": {action_: "output"},
            },
            else: {
                "0|1|2": {
                    action_: "beginsWithBond=false",
                    revisit: true,
                    toContinue: true,
                },
            },
            oxidation$: {
                0: {action_: "oxidation-output"},
            },
            CMT: {
                r: {action_: "rdt=", nextState: "rt"},
                rd: {action_: "rqt=", nextState: "rdt"},
            },
            arrowUpDown: {
                "0|1|2|as": {
                    action_: ["sb=false", "output", "operator"],
                    nextState: "1",
                },
            },
            uprightEntities: {
                "0|1|2": {action_: ["o=", "output"], nextState: "1"},
            },
            orbital: {
                "0|1|2|3": {action_: "o=", nextState: "o"},
            },
            "->": {
                "0|1|2|3": {action_: "r=", nextState: "r"},
                "a|as": {action_: ["output", "r="], nextState: "r"},
                "*": {action_: ["output", "r="], nextState: "r"},
            },
            "+": {
                o: {action_: "d= kv", nextState: "d"},
                "d|D": {action_: "d=", nextState: "d"},
                q: {action_: "d=", nextState: "qd"},
                "qd|qD": {action_: "d=", nextState: "qd"},
                dq: {action_: ["output", "d="], nextState: "d"},
                3: {
                    action_: ["sb=false", "output", "operator"],
                    nextState: "0",
                },
            },
            amount: {
                "0|2": {action_: "a=", nextState: "a"},
            },
            "pm-operator": {
                "0|1|2|a|as": {
                    action_: [
                        "sb=false",
                        "output",
                        {type_: "operator", option: "\\pm"},
                    ],
                    nextState: "0",
                },
            },
            operator: {
                "0|1|2|a|as": {
                    action_: ["sb=false", "output", "operator"],
                    nextState: "0",
                },
            },
            "-$": {
                "o|q": {action_: ["charge or bond", "output"], nextState: "qd"},
                d: {action_: "d=", nextState: "d"},
                D: {
                    action_: ["output", {type_: "bond", option: "-"}],
                    nextState: "3",
                },
                q: {action_: "d=", nextState: "qd"},
                qd: {action_: "d=", nextState: "qd"},
                "qD|dq": {
                    action_: ["output", {type_: "bond", option: "-"}],
                    nextState: "3",
                },
            },
            "-9": {
                "3|o": {
                    action_: ["output", {type_: "insert", option: "hyphen"}],
                    nextState: "3",
                },
            },
            "- orbital overlap": {
                o: {
                    action_: ["output", {type_: "insert", option: "hyphen"}],
                    nextState: "2",
                },
                d: {
                    action_: ["output", {type_: "insert", option: "hyphen"}],
                    nextState: "2",
                },
            },
            "-": {
                "0|1|2": {
                    action_: [
                        {type_: "output", option: 1},
                        "beginsWithBond=true",
                        {type_: "bond", option: "-"},
                    ],
                    nextState: "3",
                },
                3: {action_: {type_: "bond", option: "-"}},
                a: {
                    action_: ["output", {type_: "insert", option: "hyphen"}],
                    nextState: "2",
                },
                as: {
                    action_: [
                        {type_: "output", option: 2},
                        {type_: "bond", option: "-"},
                    ],
                    nextState: "3",
                },
                b: {action_: "b="},
                o: {
                    action_: {type_: "- after o/d", option: false},
                    nextState: "2",
                },
                q: {
                    action_: {type_: "- after o/d", option: false},
                    nextState: "2",
                },
                "d|qd|dq": {
                    action_: {type_: "- after o/d", option: true},
                    nextState: "2",
                },
                "D|qD|p": {
                    action_: ["output", {type_: "bond", option: "-"}],
                    nextState: "3",
                },
            },
            amount2: {
                "1|3": {action_: "a=", nextState: "a"},
            },
            letters: {
                "0|1|2|3|a|as|b|p|bp|o": {action_: "o=", nextState: "o"},
                "q|dq": {action_: ["output", "o="], nextState: "o"},
                "d|D|qd|qD": {action_: "o after d", nextState: "o"},
            },
            digits: {
                o: {action_: "q=", nextState: "q"},
                "d|D": {action_: "q=", nextState: "dq"},
                q: {action_: ["output", "o="], nextState: "o"},
                a: {action_: "o=", nextState: "o"},
            },
            "space A": {
                "b|p|bp": {},
            },
            space: {
                a: {nextState: "as"},
                0: {action_: "sb=false"},
                "1|2": {action_: "sb=true"},
                "r|rt|rd|rdt|rdq": {action_: "output", nextState: "0"},
                "*": {action_: ["output", "sb=true"], nextState: "1"},
            },
            "1st-level escape": {
                "1|2": {
                    action_: [
                        "output",
                        {type_: "insert+p1", option: "1st-level escape"},
                    ],
                },
                "*": {
                    action_: [
                        "output",
                        {type_: "insert+p1", option: "1st-level escape"},
                    ],
                    nextState: "0",
                },
            },
            "[(...)]": {
                "r|rt": {action_: "rd=", nextState: "rd"},
                "rd|rdt": {action_: "rq=", nextState: "rdq"},
            },
            "...": {
                "o|d|D|dq|qd|qD": {
                    action_: ["output", {type_: "bond", option: "..."}],
                    nextState: "3",
                },
                "*": {
                    action_: [
                        {type_: "output", option: 1},
                        {type_: "insert", option: "ellipsis"},
                    ],
                    nextState: "1",
                },
            },
            ". |* ": {
                "*": {
                    action_: [
                        "output",
                        {type_: "insert", option: "addition compound"},
                    ],
                    nextState: "1",
                },
            },
            "state of aggregation $": {
                "*": {
                    action_: ["output", "state of aggregation"],
                    nextState: "1",
                },
            },
            "{[(": {
                "a|as|o": {
                    action_: ["o=", "output", "parenthesisLevel++"],
                    nextState: "2",
                },
                "0|1|2|3": {
                    action_: ["o=", "output", "parenthesisLevel++"],
                    nextState: "2",
                },
                "*": {
                    action_: ["output", "o=", "output", "parenthesisLevel++"],
                    nextState: "2",
                },
            },
            ")]}": {
                "0|1|2|3|b|p|bp|o": {
                    action_: ["o=", "parenthesisLevel--"],
                    nextState: "o",
                },
                "a|as|d|D|q|qd|qD|dq": {
                    action_: ["output", "o=", "parenthesisLevel--"],
                    nextState: "o",
                },
            },
            ", ": {
                "*": {action_: ["output", "comma"], nextState: "0"},
            },
            "^_": {
                // ^ and _ without a sensible argument
                "*": {},
            },
            "^{(...)}|^($...$)": {
                "0|1|2|as": {action_: "b=", nextState: "b"},
                p: {action_: "b=", nextState: "bp"},
                "3|o": {action_: "d= kv", nextState: "D"},
                q: {action_: "d=", nextState: "qD"},
                "d|D|qd|qD|dq": {action_: ["output", "d="], nextState: "D"},
            },
            "^a|^\\x{}{}|^\\x{}|^\\x|'": {
                "0|1|2|as": {action_: "b=", nextState: "b"},
                p: {action_: "b=", nextState: "bp"},
                "3|o": {action_: "d= kv", nextState: "d"},
                q: {action_: "d=", nextState: "qd"},
                "d|qd|D|qD": {action_: "d="},
                dq: {action_: ["output", "d="], nextState: "d"},
            },
            "_{(state of aggregation)}$": {
                "d|D|q|qd|qD|dq": {action_: ["output", "q="], nextState: "q"},
            },
            "_{(...)}|_($...$)|_9|_\\x{}{}|_\\x{}|_\\x": {
                "0|1|2|as": {action_: "p=", nextState: "p"},
                b: {action_: "p=", nextState: "bp"},
                "3|o": {action_: "q=", nextState: "q"},
                "d|D": {action_: "q=", nextState: "dq"},
                "q|qd|qD|dq": {action_: ["output", "q="], nextState: "q"},
            },
            "=<>": {
                "0|1|2|3|a|as|o|q|d|D|qd|qD|dq": {
                    action_: [{type_: "output", option: 2}, "bond"],
                    nextState: "3",
                },
            },
            "#": {
                "0|1|2|3|a|as|o": {
                    action_: [
                        {type_: "output", option: 2},
                        {type_: "bond", option: "#"},
                    ],
                    nextState: "3",
                },
            },
            "{}": {
                "*": {action_: {type_: "output", option: 1}, nextState: "1"},
            },
            "{...}": {
                "0|1|2|3|a|as|b|p|bp": {action_: "o=", nextState: "o"},
                "o|d|D|q|qd|qD|dq": {action_: ["output", "o="], nextState: "o"},
            },
            "$...$": {
                a: {action_: "a="}, // 2$n$
                "0|1|2|3|as|b|p|bp|o": {action_: "o=", nextState: "o"}, // not 'amount'
                "as|o": {action_: "o="},
                "q|d|D|qd|qD|dq": {action_: ["output", "o="], nextState: "o"},
            },
            "\\bond{(...)}": {
                "*": {
                    action_: [{type_: "output", option: 2}, "bond"],
                    nextState: "3",
                },
            },
            "\\frac{(...)}": {
                "*": {
                    action_: [{type_: "output", option: 1}, "frac-output"],
                    nextState: "3",
                },
            },
            "\\overset{(...)}": {
                "*": {
                    action_: [{type_: "output", option: 2}, "overset-output"],
                    nextState: "3",
                },
            },
            "\\underset{(...)}": {
                "*": {
                    action_: [{type_: "output", option: 2}, "underset-output"],
                    nextState: "3",
                },
            },
            "\\underbrace{(...)}": {
                "*": {
                    action_: [
                        {type_: "output", option: 2},
                        "underbrace-output",
                    ],
                    nextState: "3",
                },
            },
            "\\color{(...)}{(...)}1|\\color(...){(...)}2": {
                "*": {
                    action_: [{type_: "output", option: 2}, "color-output"],
                    nextState: "3",
                },
            },
            "\\color{(...)}0": {
                "*": {action_: [{type_: "output", option: 2}, "color0-output"]},
            },
            "\\ce{(...)}": {
                "*": {
                    action_: [{type_: "output", option: 2}, "ce"],
                    nextState: "3",
                },
            },
            "\\,": {
                "*": {
                    action_: [{type_: "output", option: 1}, "copy"],
                    nextState: "1",
                },
            },
            "\\x{}{}|\\x{}|\\x": {
                "0|1|2|3|a|as|b|p|bp|o|c0": {
                    action_: ["o=", "output"],
                    nextState: "3",
                },
                "*": {action_: ["output", "o=", "output"], nextState: "3"},
            },
            others: {
                "*": {
                    action_: [{type_: "output", option: 1}, "copy"],
                    nextState: "3",
                },
            },
            else2: {
                a: {action_: "a to o", nextState: "o", revisit: true},
                as: {
                    action_: ["output", "sb=true"],
                    nextState: "1",
                    revisit: true,
                },
                "r|rt|rd|rdt|rdq": {
                    action_: ["output"],
                    nextState: "0",
                    revisit: true,
                },
                "*": {action_: ["output", "copy"], nextState: "3"},
            },
        }),
        actions: {
            "o after d": function (buffer, m) {
                let ret;
                if ((buffer.d || "").match(/^[0-9]+$/)) {
                    const tmp = buffer.d;
                    buffer.d = undefined;
                    ret = this["output"](buffer);
                    buffer.b = tmp;
                } else {
                    ret = this["output"](buffer);
                }
                mhchemParser.actions["o="](buffer, m);
                return ret;
            },
            "d= kv": function (buffer, m) {
                buffer.d = m;
                buffer.dType = "kv";
            },
            "charge or bond": function (buffer, m) {
                if (buffer["beginsWithBond"]) {
                    /** @type {ParserOutput[]} */
                    const ret = [];
                    mhchemParser.concatArray(ret, this["output"](buffer));
                    mhchemParser.concatArray(
                        ret,
                        mhchemParser.actions["bond"](buffer, m, "-"),
                    );
                    return ret;
                }
                buffer.d = m;
            },
            "- after o/d": function (buffer, m, isAfterD) {
                let c1 = mhchemParser.patterns.match_(
                    "orbital",
                    buffer.o || "",
                );
                const c2 = mhchemParser.patterns.match_(
                    "one lowercase greek letter $",
                    buffer.o || "",
                );
                const c3 = mhchemParser.patterns.match_(
                    "one lowercase latin letter $",
                    buffer.o || "",
                );
                const c4 = mhchemParser.patterns.match_(
                    "$one lowercase latin letter$ $",
                    buffer.o || "",
                );
                const hyphenFollows =
                    m === "-" &&
                    ((c1 && c1.remainder === "") || c2 || c3 || c4);
                if (
                    hyphenFollows &&
                    !buffer.a &&
                    !buffer.b &&
                    !buffer.p &&
                    !buffer.d &&
                    !buffer.q &&
                    !c1 &&
                    c3
                ) {
                    buffer.o = "$" + buffer.o + "$";
                }
                /** @type {ParserOutput[]} */
                const ret = [];
                if (hyphenFollows) {
                    mhchemParser.concatArray(ret, this["output"](buffer));
                    ret.push({type_: "hyphen"});
                } else {
                    c1 = mhchemParser.patterns.match_("digits", buffer.d || "");
                    if (isAfterD && c1 && c1.remainder === "") {
                        mhchemParser.concatArray(
                            ret,
                            mhchemParser.actions["d="](buffer, m),
                        );
                        mhchemParser.concatArray(ret, this["output"](buffer));
                    } else {
                        mhchemParser.concatArray(ret, this["output"](buffer));
                        mhchemParser.concatArray(
                            ret,
                            mhchemParser.actions["bond"](buffer, m, "-"),
                        );
                    }
                }
                return ret;
            },
            "a to o": function (buffer) {
                buffer.o = buffer.a;
                buffer.a = undefined;
            },
            "sb=true": function (buffer) {
                buffer.sb = true;
            },
            "sb=false": function (buffer) {
                buffer.sb = false;
            },
            "beginsWithBond=true": function (buffer) {
                buffer["beginsWithBond"] = true;
            },
            "beginsWithBond=false": function (buffer) {
                buffer["beginsWithBond"] = false;
            },
            "parenthesisLevel++": function (buffer) {
                buffer["parenthesisLevel"]++;
            },
            "parenthesisLevel--": function (buffer) {
                buffer["parenthesisLevel"]--;
            },
            "state of aggregation": function (buffer, m) {
                return {
                    type_: "state of aggregation",
                    p1: mhchemParser.go(m, "o"),
                };
            },
            comma: function (buffer, m) {
                const a = m.replace(/\s*$/, "");
                const withSpace = a !== m;
                if (withSpace && buffer["parenthesisLevel"] === 0) {
                    return {type_: "comma enumeration L", p1: a};
                }
                return {type_: "comma enumeration M", p1: a};
            },
            output: function (buffer, m, entityFollows) {
                // entityFollows:
                //   undefined = if we have nothing else to output, also ignore the just read space (buffer.sb)
                //   1 = an entity follows, never omit the space if there was one just read before (can only apply to state 1)
                //   2 = 1 + the entity can have an amount, so output a\, instead of converting it to o (can only apply to states a|as)
                /** @type {ParserOutput | ParserOutput[]} */
                let ret;
                if (!buffer.r) {
                    ret = [];
                    if (
                        !buffer.a &&
                        !buffer.b &&
                        !buffer.p &&
                        !buffer.o &&
                        !buffer.q &&
                        !buffer.d &&
                        !entityFollows
                    ) {
                        //ret = [];
                    } else {
                        if (buffer.sb) {
                            ret.push({type_: "entitySkip"});
                        }
                        if (
                            !buffer.o &&
                            !buffer.q &&
                            !buffer.d &&
                            !buffer.b &&
                            !buffer.p &&
                            entityFollows !== 2
                        ) {
                            buffer.o = buffer.a;
                            buffer.a = undefined;
                        } else if (
                            !buffer.o &&
                            !buffer.q &&
                            !buffer.d &&
                            (buffer.b || buffer.p)
                        ) {
                            buffer.o = buffer.a;
                            buffer.d = buffer.b;
                            buffer.q = buffer.p;
                            buffer.a = buffer.b = buffer.p = undefined;
                        } else {
                            if (
                                buffer.o &&
                                buffer.dType === "kv" &&
                                mhchemParser.patterns.match_(
                                    "d-oxidation$",
                                    buffer.d || "",
                                )
                            ) {
                                buffer.dType = "oxidation";
                            } else if (
                                buffer.o &&
                                buffer.dType === "kv" &&
                                !buffer.q
                            ) {
                                buffer.dType = undefined;
                            }
                        }
                        ret.push({
                            type_: "chemfive",
                            a: mhchemParser.go(buffer.a, "a"),
                            b: mhchemParser.go(buffer.b, "bd"),
                            p: mhchemParser.go(buffer.p, "pq"),
                            o: mhchemParser.go(buffer.o, "o"),
                            q: mhchemParser.go(buffer.q, "pq"),
                            d: mhchemParser.go(
                                buffer.d,
                                buffer.dType === "oxidation"
                                    ? "oxidation"
                                    : "bd",
                            ),
                            dType: buffer.dType,
                        });
                    }
                } else {
                    // r
                    /** @type {ParserOutput[]} */
                    let rd;
                    if (buffer.rdt === "M") {
                        rd = mhchemParser.go(buffer.rd, "tex-math");
                    } else if (buffer.rdt === "T") {
                        rd = [{type_: "text", p1: buffer.rd || ""}];
                    } else {
                        rd = mhchemParser.go(buffer.rd);
                    }
                    /** @type {ParserOutput[]} */
                    let rq;
                    if (buffer.rqt === "M") {
                        rq = mhchemParser.go(buffer.rq, "tex-math");
                    } else if (buffer.rqt === "T") {
                        rq = [{type_: "text", p1: buffer.rq || ""}];
                    } else {
                        rq = mhchemParser.go(buffer.rq);
                    }
                    ret = {
                        type_: "arrow",
                        r: buffer.r,
                        rd: rd,
                        rq: rq,
                    };
                }
                for (const p in buffer) {
                    if (p !== "parenthesisLevel" && p !== "beginsWithBond") {
                        delete buffer[p];
                    }
                }
                return ret;
            },
            "oxidation-output": function (buffer, m) {
                const ret = ["{"];
                mhchemParser.concatArray(ret, mhchemParser.go(m, "oxidation"));
                ret.push("}");
                return ret;
            },
            "frac-output": function (buffer, m) {
                return {
                    type_: "frac-ce",
                    p1: mhchemParser.go(m[0]),
                    p2: mhchemParser.go(m[1]),
                };
            },
            "overset-output": function (buffer, m) {
                return {
                    type_: "overset",
                    p1: mhchemParser.go(m[0]),
                    p2: mhchemParser.go(m[1]),
                };
            },
            "underset-output": function (buffer, m) {
                return {
                    type_: "underset",
                    p1: mhchemParser.go(m[0]),
                    p2: mhchemParser.go(m[1]),
                };
            },
            "underbrace-output": function (buffer, m) {
                return {
                    type_: "underbrace",
                    p1: mhchemParser.go(m[0]),
                    p2: mhchemParser.go(m[1]),
                };
            },
            "color-output": function (buffer, m) {
                return {
                    type_: "color",
                    color1: m[0],
                    color2: mhchemParser.go(m[1]),
                };
            },
            "r=": function (buffer, m) {
                buffer.r = m;
            },
            "rdt=": function (buffer, m) {
                buffer.rdt = m;
            },
            "rd=": function (buffer, m) {
                buffer.rd = m;
            },
            "rqt=": function (buffer, m) {
                buffer.rqt = m;
            },
            "rq=": function (buffer, m) {
                buffer.rq = m;
            },
            operator: function (buffer, m, p1) {
                return {type_: "operator", kind_: p1 || m};
            },
        },
    },
    a: {
        transitions: mhchemParser.createTransitions({
            empty: {
                "*": {},
            },
            "1/2$": {
                0: {action_: "1/2"},
            },
            else: {
                0: {nextState: "1", revisit: true},
            },
            "$(...)$": {
                "*": {action_: "tex-math tight", nextState: "1"},
            },
            ",": {
                "*": {action_: {type_: "insert", option: "commaDecimal"}},
            },
            else2: {
                "*": {action_: "copy"},
            },
        }),
        actions: {},
    },
    o: {
        transitions: mhchemParser.createTransitions({
            empty: {
                "*": {},
            },
            "1/2$": {
                0: {action_: "1/2"},
            },
            else: {
                0: {nextState: "1", revisit: true},
            },
            letters: {
                "*": {action_: "rm"},
            },
            "\\ca": {
                "*": {action_: {type_: "insert", option: "circa"}},
            },
            "\\x{}{}|\\x{}|\\x": {
                "*": {action_: "copy"},
            },
            "${(...)}$|$(...)$": {
                "*": {action_: "tex-math"},
            },
            "{(...)}": {
                "*": {action_: "{text}"},
            },
            else2: {
                "*": {action_: "copy"},
            },
        }),
        actions: {},
    },
    text: {
        transitions: mhchemParser.createTransitions({
            empty: {
                "*": {action_: "output"},
            },
            "{...}": {
                "*": {action_: "text="},
            },
            "${(...)}$|$(...)$": {
                "*": {action_: "tex-math"},
            },
            "\\greek": {
                "*": {action_: ["output", "rm"]},
            },
            "\\,|\\x{}{}|\\x{}|\\x": {
                "*": {action_: ["output", "copy"]},
            },
            else: {
                "*": {action_: "text="},
            },
        }),
        actions: {
            output: function (buffer) {
                if (buffer.text_) {
                    /** @type {ParserOutput} */
                    const ret = {type_: "text", p1: buffer.text_};
                    for (const p in buffer) {
                        delete buffer[p];
                    }
                    return ret;
                }
            },
        },
    },
    pq: {
        transitions: mhchemParser.createTransitions({
            empty: {
                "*": {},
            },
            "state of aggregation $": {
                "*": {action_: "state of aggregation"},
            },
            i$: {
                0: {nextState: "!f", revisit: true},
            },
            "(KV letters),": {
                0: {action_: "rm", nextState: "0"},
            },
            formula$: {
                0: {nextState: "f", revisit: true},
            },
            "1/2$": {
                0: {action_: "1/2"},
            },
            else: {
                0: {nextState: "!f", revisit: true},
            },
            "${(...)}$|$(...)$": {
                "*": {action_: "tex-math"},
            },
            "{(...)}": {
                "*": {action_: "text"},
            },
            "a-z": {
                f: {action_: "tex-math"},
            },
            letters: {
                "*": {action_: "rm"},
            },
            "-9.,9": {
                "*": {action_: "9,9"},
            },
            ",": {
                "*": {
                    action_: {
                        type_: "insert+p1",
                        option: "comma enumeration S",
                    },
                },
            },
            "\\color{(...)}{(...)}1|\\color(...){(...)}2": {
                "*": {action_: "color-output"},
            },
            "\\color{(...)}0": {
                "*": {action_: "color0-output"},
            },
            "\\ce{(...)}": {
                "*": {action_: "ce"},
            },
            "\\,|\\x{}{}|\\x{}|\\x": {
                "*": {action_: "copy"},
            },
            else2: {
                "*": {action_: "copy"},
            },
        }),
        actions: {
            "state of aggregation": function (buffer, m) {
                return {
                    type_: "state of aggregation subscript",
                    p1: mhchemParser.go(m, "o"),
                };
            },
            "color-output": function (buffer, m) {
                return {
                    type_: "color",
                    color1: m[0],
                    color2: mhchemParser.go(m[1], "pq"),
                };
            },
        },
    },
    bd: {
        transitions: mhchemParser.createTransitions({
            empty: {
                "*": {},
            },
            x$: {
                0: {nextState: "!f", revisit: true},
            },
            formula$: {
                0: {nextState: "f", revisit: true},
            },
            else: {
                0: {nextState: "!f", revisit: true},
            },
            "-9.,9 no missing 0": {
                "*": {action_: "9,9"},
            },
            ".": {
                "*": {action_: {type_: "insert", option: "electron dot"}},
            },
            "a-z": {
                f: {action_: "tex-math"},
            },
            x: {
                "*": {action_: {type_: "insert", option: "KV x"}},
            },
            letters: {
                "*": {action_: "rm"},
            },
            "'": {
                "*": {action_: {type_: "insert", option: "prime"}},
            },
            "${(...)}$|$(...)$": {
                "*": {action_: "tex-math"},
            },
            "{(...)}": {
                "*": {action_: "text"},
            },
            "\\color{(...)}{(...)}1|\\color(...){(...)}2": {
                "*": {action_: "color-output"},
            },
            "\\color{(...)}0": {
                "*": {action_: "color0-output"},
            },
            "\\ce{(...)}": {
                "*": {action_: "ce"},
            },
            "\\,|\\x{}{}|\\x{}|\\x": {
                "*": {action_: "copy"},
            },
            else2: {
                "*": {action_: "copy"},
            },
        }),
        actions: {
            "color-output": function (buffer, m) {
                return {
                    type_: "color",
                    color1: m[0],
                    color2: mhchemParser.go(m[1], "bd"),
                };
            },
        },
    },
    oxidation: {
        transitions: mhchemParser.createTransitions({
            empty: {
                "*": {},
            },
            "roman numeral": {
                "*": {action_: "roman-numeral"},
            },
            "${(...)}$|$(...)$": {
                "*": {action_: "tex-math"},
            },
            else: {
                "*": {action_: "copy"},
            },
        }),
        actions: {
            "roman-numeral": function (buffer, m) {
                return {type_: "roman numeral", p1: m || ""};
            },
        },
    },
    "tex-math": {
        transitions: mhchemParser.createTransitions({
            empty: {
                "*": {action_: "output"},
            },
            "\\ce{(...)}": {
                "*": {action_: ["output", "ce"]},
            },
            "{...}|\\,|\\x{}{}|\\x{}|\\x": {
                "*": {action_: "o="},
            },
            else: {
                "*": {action_: "o="},
            },
        }),
        actions: {
            output: function (buffer) {
                if (buffer.o) {
                    /** @type {ParserOutput} */
                    const ret = {type_: "tex-math", p1: buffer.o};
                    for (const p in buffer) {
                        delete buffer[p];
                    }
                    return ret;
                }
            },
        },
    },
    "tex-math tight": {
        transitions: mhchemParser.createTransitions({
            empty: {
                "*": {action_: "output"},
            },
            "\\ce{(...)}": {
                "*": {action_: ["output", "ce"]},
            },
            "{...}|\\,|\\x{}{}|\\x{}|\\x": {
                "*": {action_: "o="},
            },
            "-|+": {
                "*": {action_: "tight operator"},
            },
            else: {
                "*": {action_: "o="},
            },
        }),
        actions: {
            "tight operator": function (buffer, m) {
                buffer.o = (buffer.o || "") + "{" + m + "}";
            },
            output: function (buffer) {
                if (buffer.o) {
                    /** @type {ParserOutput} */
                    const ret = {type_: "tex-math", p1: buffer.o};
                    for (const p in buffer) {
                        delete buffer[p];
                    }
                    return ret;
                }
            },
        },
    },
    "9,9": {
        transitions: mhchemParser.createTransitions({
            empty: {
                "*": {},
            },
            ",": {
                "*": {action_: "comma"},
            },
            else: {
                "*": {action_: "copy"},
            },
        }),
        actions: {
            comma: function () {
                return {type_: "commaDecimal"};
            },
        },
    },
    //#endregion
    //
    // \pu state machines
    //
    //#region pu
    pu: {
        transitions: mhchemParser.createTransitions({
            empty: {
                "*": {action_: "output"},
            },
            space$: {
                "*": {action_: ["output", "space"]},
            },
            "{[(|)]}": {
                "0|a": {action_: "copy"},
            },
            "(-)(9)^(-9)": {
                0: {action_: "number^", nextState: "a"},
            },
            "(-)(9.,9)(e)(99)": {
                0: {action_: "enumber", nextState: "a"},
            },
            space: {
                "0|a": {},
            },
            "pm-operator": {
                "0|a": {
                    action_: {type_: "operator", option: "\\pm"},
                    nextState: "0",
                },
            },
            operator: {
                "0|a": {action_: "copy", nextState: "0"},
            },
            "//": {
                d: {action_: "o=", nextState: "/"},
            },
            "/": {
                d: {action_: "o=", nextState: "/"},
            },
            "{...}|else": {
                "0|d": {action_: "d=", nextState: "d"},
                a: {action_: ["space", "d="], nextState: "d"},
                "/|q": {action_: "q=", nextState: "q"},
            },
        }),
        actions: {
            enumber: function (buffer, m) {
                /** @type {ParserOutput[]} */
                const ret = [];
                if (m[0] === "+-" || m[0] === "+/-") {
                    ret.push("\\pm ");
                } else if (m[0]) {
                    ret.push(m[0]);
                }
                if (m[1]) {
                    mhchemParser.concatArray(
                        ret,
                        mhchemParser.go(m[1], "pu-9,9"),
                    );
                    if (m[2]) {
                        if (m[2].match(/[,.]/)) {
                            mhchemParser.concatArray(
                                ret,
                                mhchemParser.go(m[2], "pu-9,9"),
                            );
                        } else {
                            ret.push(m[2]);
                        }
                    }
                    m[3] = m[4] || m[3];
                    if (m[3]) {
                        m[3] = m[3].trim();
                        if (m[3] === "e" || m[3].substr(0, 1) === "*") {
                            ret.push({type_: "cdot"});
                        } else {
                            ret.push({type_: "times"});
                        }
                    }
                }
                if (m[3]) {
                    ret.push("10^{" + m[5] + "}");
                }
                return ret;
            },
            "number^": function (buffer, m) {
                /** @type {ParserOutput[]} */
                const ret = [];
                if (m[0] === "+-" || m[0] === "+/-") {
                    ret.push("\\pm ");
                } else if (m[0]) {
                    ret.push(m[0]);
                }
                mhchemParser.concatArray(ret, mhchemParser.go(m[1], "pu-9,9"));
                ret.push("^{" + m[2] + "}");
                return ret;
            },
            operator: function (buffer, m, p1) {
                return {type_: "operator", kind_: p1 || m};
            },
            space: function () {
                return {type_: "pu-space-1"};
            },
            output: function (buffer) {
                /** @type {ParserOutput | ParserOutput[]} */
                let ret;
                const md = mhchemParser.patterns.match_(
                    "{(...)}",
                    buffer.d || "",
                );
                if (md && md.remainder === "") {
                    buffer.d = md.match_;
                }
                const mq = mhchemParser.patterns.match_(
                    "{(...)}",
                    buffer.q || "",
                );
                if (mq && mq.remainder === "") {
                    buffer.q = mq.match_;
                }
                if (buffer.d) {
                    buffer.d = buffer.d.replace(
                        /\u00B0C|\^oC|\^{o}C/g,
                        "{}^{\\circ}C",
                    );
                    buffer.d = buffer.d.replace(
                        /\u00B0F|\^oF|\^{o}F/g,
                        "{}^{\\circ}F",
                    );
                }
                if (buffer.q) {
                    // fraction
                    buffer.q = buffer.q.replace(
                        /\u00B0C|\^oC|\^{o}C/g,
                        "{}^{\\circ}C",
                    );
                    buffer.q = buffer.q.replace(
                        /\u00B0F|\^oF|\^{o}F/g,
                        "{}^{\\circ}F",
                    );
                    const b5 = {
                        d: mhchemParser.go(buffer.d, "pu"),
                        q: mhchemParser.go(buffer.q, "pu"),
                    };
                    if (buffer.o === "//") {
                        ret = {type_: "pu-frac", p1: b5.d, p2: b5.q};
                    } else {
                        ret = b5.d;
                        if (b5.d.length > 1 || b5.q.length > 1) {
                            ret.push({type_: " / "});
                        } else {
                            ret.push({type_: "/"});
                        }
                        mhchemParser.concatArray(ret, b5.q);
                    }
                } else {
                    // no fraction
                    ret = mhchemParser.go(buffer.d, "pu-2");
                }
                for (const p in buffer) {
                    delete buffer[p];
                }
                return ret;
            },
        },
    },
    "pu-2": {
        transitions: mhchemParser.createTransitions({
            empty: {
                "*": {action_: "output"},
            },
            "*": {
                "*": {action_: ["output", "cdot"], nextState: "0"},
            },
            "\\x": {
                "*": {action_: "rm="},
            },
            space: {
                "*": {action_: ["output", "space"], nextState: "0"},
            },
            "^{(...)}|^(-1)": {
                1: {action_: "^(-1)"},
            },
            "-9.,9": {
                0: {action_: "rm=", nextState: "0"},
                1: {action_: "^(-1)", nextState: "0"},
            },
            "{...}|else": {
                "*": {action_: "rm=", nextState: "1"},
            },
        }),
        actions: {
            cdot: function () {
                return {type_: "tight cdot"};
            },
            "^(-1)": function (buffer, m) {
                buffer.rm += "^{" + m + "}";
            },
            space: function () {
                return {type_: "pu-space-2"};
            },
            output: function (buffer) {
                /** @type {ParserOutput | ParserOutput[]} */
                let ret = [];
                if (buffer.rm) {
                    const mrm = mhchemParser.patterns.match_(
                        "{(...)}",
                        buffer.rm || "",
                    );
                    if (mrm && mrm.remainder === "") {
                        ret = mhchemParser.go(mrm.match_, "pu");
                    } else {
                        ret = {type_: "rm", p1: buffer.rm};
                    }
                }
                for (const p in buffer) {
                    delete buffer[p];
                }
                return ret;
            },
        },
    },
    "pu-9,9": {
        transitions: mhchemParser.createTransitions({
            empty: {
                0: {action_: "output-0"},
                o: {action_: "output-o"},
            },
            ",": {
                0: {action_: ["output-0", "comma"], nextState: "o"},
            },
            ".": {
                0: {action_: ["output-0", "copy"], nextState: "o"},
            },
            else: {
                "*": {action_: "text="},
            },
        }),
        actions: {
            comma: function () {
                return {type_: "commaDecimal"};
            },
            "output-0": function (buffer) {
                /** @type {ParserOutput[]} */
                const ret = [];
                buffer.text_ = buffer.text_ || "";
                if (buffer.text_.length > 4) {
                    let a = buffer.text_.length % 3;
                    if (a === 0) {
                        a = 3;
                    }
                    for (let i = buffer.text_.length - 3; i > 0; i -= 3) {
                        ret.push(buffer.text_.substr(i, 3));
                        ret.push({type_: "1000 separator"});
                    }
                    ret.push(buffer.text_.substr(0, a));
                    ret.reverse();
                } else {
                    ret.push(buffer.text_);
                }
                for (const p in buffer) {
                    delete buffer[p];
                }
                return ret;
            },
            "output-o": function (buffer) {
                /** @type {ParserOutput[]} */
                const ret = [];
                buffer.text_ = buffer.text_ || "";
                if (buffer.text_.length > 4) {
                    const a = buffer.text_.length - 3;
                    for (var i = 0; i < a; i += 3) {
                        ret.push(buffer.text_.substr(i, 3));
                        ret.push({type_: "1000 separator"});
                    }
                    ret.push(buffer.text_.substr(i));
                } else {
                    ret.push(buffer.text_);
                }
                for (const p in buffer) {
                    delete buffer[p];
                }
                return ret;
            },
        },
    },
    //#endregion
};

//
// texify: Take MhchemParser output and convert it to TeX
//
/** @type {Texify} */
var texify = {
    go: function (input, isInner) {
        // (recursive, max 4 levels)
        if (!input) {
            return "";
        }
        let res = "";
        let cee = false;
        for (let i = 0; i < input.length; i++) {
            const inputi = input[i];
            if (typeof inputi === "string") {
                res += inputi;
            } else {
                res += texify._go2(inputi);
                if (inputi.type_ === "1st-level escape") {
                    cee = true;
                }
            }
        }
        if (!isInner && !cee && res) {
            res = "{" + res + "}";
        }
        return res;
    },
    _goInner: function (input) {
        if (!input) {
            return input;
        }
        return texify.go(input, true);
    },
    _go2: function (buf) {
        /** @type {undefined | string} */
        let res;
        switch (buf.type_) {
            case "chemfive":
                res = "";
                var b5 = {
                    a: texify._goInner(buf.a),
                    b: texify._goInner(buf.b),
                    p: texify._goInner(buf.p),
                    o: texify._goInner(buf.o),
                    q: texify._goInner(buf.q),
                    d: texify._goInner(buf.d),
                };
                //
                // a
                //
                if (b5.a) {
                    if (b5.a.match(/^[+\-]/)) {
                        b5.a = "{" + b5.a + "}";
                    }
                    res += b5.a + "\\,";
                }
                //
                // b and p
                //
                if (b5.b || b5.p) {
                    res += "{\\vphantom{X}}";
                    res +=
                        "^{\\hphantom{" +
                        (b5.b || "") +
                        "}}_{\\hphantom{" +
                        (b5.p || "") +
                        "}}";
                    res += "{\\vphantom{X}}";
                    res +=
                        "^{\\smash[t]{\\vphantom{2}}\\mathllap{" +
                        (b5.b || "") +
                        "}}";
                    res +=
                        "_{\\vphantom{2}\\mathllap{\\smash[t]{" +
                        (b5.p || "") +
                        "}}}";
                }
                //
                // o
                //
                if (b5.o) {
                    if (b5.o.match(/^[+\-]/)) {
                        b5.o = "{" + b5.o + "}";
                    }
                    res += b5.o;
                }
                //
                // q and d
                //
                if (buf.dType === "kv") {
                    if (b5.d || b5.q) {
                        res += "{\\vphantom{X}}";
                    }
                    if (b5.d) {
                        res += "^{" + b5.d + "}";
                    }
                    if (b5.q) {
                        res += "_{\\smash[t]{" + b5.q + "}}";
                    }
                } else if (buf.dType === "oxidation") {
                    if (b5.d) {
                        res += "{\\vphantom{X}}";
                        res += "^{" + b5.d + "}";
                    }
                    if (b5.q) {
                        res += "{\\vphantom{X}}";
                        res += "_{\\smash[t]{" + b5.q + "}}";
                    }
                } else {
                    if (b5.q) {
                        res += "{\\vphantom{X}}";
                        res += "_{\\smash[t]{" + b5.q + "}}";
                    }
                    if (b5.d) {
                        res += "{\\vphantom{X}}";
                        res += "^{" + b5.d + "}";
                    }
                }
                break;
            case "rm":
                res = "\\mathrm{" + buf.p1 + "}";
                break;
            case "text":
                if (buf.p1.match(/[\^_]/)) {
                    buf.p1 = buf.p1.replace(" ", "~").replace("-", "\\text{-}");
                    res = "\\mathrm{" + buf.p1 + "}";
                } else {
                    res = "\\text{" + buf.p1 + "}";
                }
                break;
            case "roman numeral":
                res = "\\mathrm{" + buf.p1 + "}";
                break;
            case "state of aggregation":
                res = "\\mskip2mu " + texify._goInner(buf.p1);
                break;
            case "state of aggregation subscript":
                res = "\\mskip1mu " + texify._goInner(buf.p1);
                break;
            case "bond":
                res = texify._getBond(buf.kind_);
                if (!res) {
                    throw [
                        "MhchemErrorBond",
                        "mhchem Error. Unknown bond type (" + buf.kind_ + ")",
                    ];
                }
                break;
            case "frac":
                var c = "\\frac{" + buf.p1 + "}{" + buf.p2 + "}";
                res =
                    "\\mathchoice{\\textstyle" +
                    c +
                    "}{" +
                    c +
                    "}{" +
                    c +
                    "}{" +
                    c +
                    "}";
                break;
            case "pu-frac":
                var d =
                    "\\frac{" +
                    texify._goInner(buf.p1) +
                    "}{" +
                    texify._goInner(buf.p2) +
                    "}";
                res =
                    "\\mathchoice{\\textstyle" +
                    d +
                    "}{" +
                    d +
                    "}{" +
                    d +
                    "}{" +
                    d +
                    "}";
                break;
            case "tex-math":
                res = buf.p1 + " ";
                break;
            case "frac-ce":
                res =
                    "\\frac{" +
                    texify._goInner(buf.p1) +
                    "}{" +
                    texify._goInner(buf.p2) +
                    "}";
                break;
            case "overset":
                res =
                    "\\overset{" +
                    texify._goInner(buf.p1) +
                    "}{" +
                    texify._goInner(buf.p2) +
                    "}";
                break;
            case "underset":
                res =
                    "\\underset{" +
                    texify._goInner(buf.p1) +
                    "}{" +
                    texify._goInner(buf.p2) +
                    "}";
                break;
            case "underbrace":
                res =
                    "\\underbrace{" +
                    texify._goInner(buf.p1) +
                    "}_{" +
                    texify._goInner(buf.p2) +
                    "}";
                break;
            case "color":
                res =
                    "{\\color{" +
                    buf.color1 +
                    "}{" +
                    texify._goInner(buf.color2) +
                    "}}";
                break;
            case "color0":
                res = "\\color{" + buf.color + "}";
                break;
            case "arrow":
                var b6 = {
                    rd: texify._goInner(buf.rd),
                    rq: texify._goInner(buf.rq),
                };
                var arrow = "\\x" + texify._getArrow(buf.r);
                if (b6.rq) {
                    arrow += "[{" + b6.rq + "}]";
                }
                if (b6.rd) {
                    arrow += "{" + b6.rd + "}";
                } else {
                    arrow += "{}";
                }
                res = arrow;
                break;
            case "operator":
                res = texify._getOperator(buf.kind_);
                break;
            case "1st-level escape":
                res = buf.p1 + " "; // &, \\\\, \\hlin
                break;
            case "space":
                res = " ";
                break;
            case "entitySkip":
                res = "~";
                break;
            case "pu-space-1":
                res = "~";
                break;
            case "pu-space-2":
                res = "\\mkern3mu ";
                break;
            case "1000 separator":
                res = "\\mkern2mu ";
                break;
            case "commaDecimal":
                res = "{,}";
                break;
            case "comma enumeration L":
                res = "{" + buf.p1 + "}\\mkern6mu ";
                break;
            case "comma enumeration M":
                res = "{" + buf.p1 + "}\\mkern3mu ";
                break;
            case "comma enumeration S":
                res = "{" + buf.p1 + "}\\mkern1mu ";
                break;
            case "hyphen":
                res = "\\text{-}";
                break;
            case "addition compound":
                res = "\\,{\\cdot}\\,";
                break;
            case "electron dot":
                res = "\\mkern1mu \\bullet\\mkern1mu ";
                break;
            case "KV x":
                res = "{\\times}";
                break;
            case "prime":
                res = "\\prime ";
                break;
            case "cdot":
                res = "\\cdot ";
                break;
            case "tight cdot":
                res = "\\mkern1mu{\\cdot}\\mkern1mu ";
                break;
            case "times":
                res = "\\times ";
                break;
            case "circa":
                res = "{\\sim}";
                break;
            case "^":
                res = "uparrow";
                break;
            case "v":
                res = "downarrow";
                break;
            case "ellipsis":
                res = "\\ldots ";
                break;
            case "/":
                res = "/";
                break;
            case " / ":
                res = "\\,/\\,";
                break;
            default:
                assertNever(buf);
                throw ["MhchemBugT", "mhchem bug T. Please report."]; // Missing texify rule or unknown MhchemParser output
        }
        assertString(res);
        return res;
    },
    _getArrow: function (a) {
        switch (a) {
            case "->":
                return "rightarrow";
            case "\u2192":
                return "rightarrow";
            case "\u27F6":
                return "rightarrow";
            case "<-":
                return "leftarrow";
            case "<->":
                return "leftrightarrow";
            case "<-->":
                return "rightleftarrows";
            case "<=>":
                return "rightleftharpoons";
            case "\u21CC":
                return "rightleftharpoons";
            case "<=>>":
                return "rightequilibrium";
            case "<<=>":
                return "leftequilibrium";
            default:
                assertNever(a);
                throw ["MhchemBugT", "mhchem bug T. Please report."];
        }
    },
    _getBond: function (a) {
        switch (a) {
            case "-":
                return "{-}";
            case "1":
                return "{-}";
            case "=":
                return "{=}";
            case "2":
                return "{=}";
            case "#":
                return "{\\equiv}";
            case "3":
                return "{\\equiv}";
            case "~":
                return "{\\tripledash}";
            case "~-":
                return "{\\mathrlap{\\raisebox{-.1em}{$-$}}\\raisebox{.1em}{$\\tripledash$}}";
            case "~=":
                return "{\\mathrlap{\\raisebox{-.2em}{$-$}}\\mathrlap{\\raisebox{.2em}{$\\tripledash$}}-}";
            case "~--":
                return "{\\mathrlap{\\raisebox{-.2em}{$-$}}\\mathrlap{\\raisebox{.2em}{$\\tripledash$}}-}";
            case "-~-":
                return "{\\mathrlap{\\raisebox{-.2em}{$-$}}\\mathrlap{\\raisebox{.2em}{$-$}}\\tripledash}";
            case "...":
                return "{{\\cdot}{\\cdot}{\\cdot}}";
            case "....":
                return "{{\\cdot}{\\cdot}{\\cdot}{\\cdot}}";
            case "->":
                return "{\\rightarrow}";
            case "<-":
                return "{\\leftarrow}";
            case "<":
                return "{<}";
            case ">":
                return "{>}";
            default:
                assertNever(a);
                throw ["MhchemBugT", "mhchem bug T. Please report."];
        }
    },
    _getOperator: function (a) {
        switch (a) {
            case "+":
                return " {}+{} ";
            case "-":
                return " {}-{} ";
            case "=":
                return " {}={} ";
            case "<":
                return " {}<{} ";
            case ">":
                return " {}>{} ";
            case "<<":
                return " {}\\ll{} ";
            case ">>":
                return " {}\\gg{} ";
            case "\\pm":
                return " {}\\pm{} ";
            case "\\approx":
                return " {}\\approx{} ";
            case "$\\approx$":
                return " {}\\approx{} ";
            case "v":
                return " \\downarrow{} ";
            case "(v)":
                return " \\downarrow{} ";
            case "^":
                return " \\uparrow{} ";
            case "(^)":
                return " \\uparrow{} ";
            default:
                assertNever(a);
                throw ["MhchemBugT", "mhchem bug T. Please report."];
        }
    },
};

//
// Helpers for code anaylsis
// Will show type error at calling position
//
/** @param {number} a */
function assertNever(a) {}
/** @param {string} a */
function assertString(a) {}
